use crate::GLItem;
use futures::{Future, IntoFuture, Stream};

pub struct CatalogProvider3 {
    http_client: hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>,
}

impl CatalogProvider3 {
    pub fn new() -> Self {
        let connector = hyper_rustls::HttpsConnector::new(2);
        Self {
            http_client: hyper::Client::builder().build(connector),
        }
    }
}

impl crate::CatalogProvider for CatalogProvider3 {
    fn load(&self, uri: &str) -> Box<Future<Item = GLItem, Error = String> + Send> {
        let uri = uri.to_owned();
        println!("load({})", uri);
        let real_uri = format!("https://www.churchofjesuschrist.org/study/api/v3/language-pages/type/dynamic?lang=eng&uri={}", percent_encoding::utf8_percent_encode(&uri, percent_encoding::DEFAULT_ENCODE_SET));
        Box::new(
            real_uri
                .parse()
                .map_err(|err| format!("Failed to construct URI: {:?}", err))
                .map(|real_uri| {
                    self.http_client
                        .get(real_uri)
                        .map_err(|err| format!("Failed to send request: {:?}", err))
                })
                .into_future()
                .and_then(|x| x)
                .and_then(|resp| {
                    resp.into_body()
                        .concat2()
                        .map_err(|err| format!("Failed to read response: {:?}", err))
                })
                .and_then(|body| {
                    println!("received response");
                    serde_json::from_slice(&body)
                        .map_err(|err| format!("Failed to parse response: {:?}", err))
                })
                .and_then(move |body: serde_json::Value| {
                    println!("{:?}", body);

                    if let Some(content) = body.get("content") {
                        let meta = content.get("meta");

                        let should_skip = if let Some(meta) = meta {
                            if body.get("toc").is_some() {
                                if let Some(content_uri) = meta
                                    .get("pageAttributes")
                                    .and_then(|attr| attr.get("data-uri"))
                                    .and_then(|uri| uri.as_str())
                                {
                                    if content_uri != uri {
                                        true
                                    } else {
                                        false
                                    }
                                } else {
                                    false
                                }
                            } else {
                                false
                            }
                        } else {
                            false
                        };

                        if !should_skip {
                            let title = meta
                                .and_then(|meta| meta.get("title"))
                                .and_then(|title| title.as_str())
                                .unwrap_or_else(|| "[Untitled]");
                            let content = match content
                                .get("content")
                                .and_then(|content| content.get("body"))
                            {
                                Some(content) => match content.as_str() {
                                    Some(content) => content,
                                    None => return Err("Content body was not a string".to_owned()),
                                },
                                None => return Err("Missing body in content".to_owned()),
                            };

                            return Ok(GLItem::Content {
                                body: content.to_owned(),
                                title: title.to_owned(),
                            });
                        }
                    }

                    if let Some(toc) = body.get("toc") {
                        let entries = match toc.get("entries") {
                            Some(entries) => match entries.as_array() {
                                Some(entries) => entries,
                                None => return Err("Entries was not an array".to_owned()),
                            },
                            None => return Err("Missing entries in toc object".to_owned()),
                        };

                        let title = match toc.get("title") {
                            Some(title) => title.to_string(),
                            None => "[Untitled]".to_owned(),
                        };

                        let items = entries.into_iter().flat_map(transform_entry).collect();

                        return Ok(GLItem::Collection { title, items });
                    }

                    if let Some(collection) = body.get("collection") {
                        let title = collection
                            .get("title")
                            .and_then(|title| title.as_str())
                            .unwrap_or_else(|| "[Untitled]")
                            .to_owned();
                        let entries = match collection.get("entries") {
                            Some(entries) => match entries.as_array() {
                                Some(entries) => entries,
                                None => return Err("Entries was not an array".to_owned()),
                            },
                            None => return Err("Missing entries in collection object".to_owned()),
                        };

                        let items = entries.into_iter().flat_map(transform_entry).collect();

                        return Ok(GLItem::Collection { title, items });
                    }

                    Err("Unknown item type".to_owned())
                }),
        )
    }
}

fn transform_entry(entry: &serde_json::Value) -> Vec<GLItem> {
    if let Some(node) = entry.get("content") {
        let uri = match node.get("uri").and_then(|value| value.as_str()) {
            Some(uri) => uri.to_owned(),
            None => {
                return vec![GLItem::Error {
                    title: "Missing URI in content block".to_owned(),
                }]
            }
        };
        let title = match node.get("title") {
            Some(title) => title.to_string(),
            None => "[Untitled]".to_owned(),
        };

        return vec![GLItem::Link { title, uri }];
    }

    if let Some(node) = entry.get("collection") {
        let uri = match node.get("uri").and_then(|value| value.as_str()) {
            Some(uri) => uri.to_owned(),
            None => {
                return vec![GLItem::Error {
                    title: "Missing URI in content block".to_owned(),
                }]
            }
        };
        let title = match node.get("title") {
            Some(title) => title.to_string(),
            None => "[Untitled]".to_owned(),
        };

        return vec![GLItem::Link { title, uri }];
    }

    if let Some(node) = entry.get("item") {
        let uri = match node.get("uri").and_then(|value| value.as_str()) {
            Some(uri) => uri.to_owned(),
            None => {
                return vec![GLItem::Error {
                    title: "Missing URI in content block".to_owned(),
                }]
            }
        };
        let title = match node.get("title") {
            Some(title) => title.to_string(),
            None => "[Untitled]".to_owned(),
        };

        return vec![GLItem::Link { title, uri }];
    }

    if let Some(node) = entry.get("section") {
        let entries = match node.get("entries") {
            Some(entries) => match entries.as_array() {
                Some(entries) => entries,
                None => {
                    return vec![GLItem::Error {
                        title: "[Error: entries was not an array]".to_owned(),
                    }]
                }
            },
            None => {
                return vec![GLItem::Error {
                    title: "[Error: missing entries in section]".to_owned(),
                }]
            }
        };

        return entries.into_iter().flat_map(transform_entry).collect();
    }

    vec![GLItem::Error {
        title: "[Error: can't render]".to_owned(),
    }]
}
